﻿
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Threading.Tasks;
using System;
using System.IO;
using System.Net;
using System.Web;
using Newtonsoft.Json.Converters;
using PServiceBus.Core.Runtime;
using Newtonsoft.Json.Linq;

namespace PServiceBus.WebAPI {
    public class JsonpMediaTypeFormatter : JavaScriptSerializerFormatter/*JsonMediaTypeFormatter*/ {
        
        public JsonpMediaTypeFormatter() {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/javascript"));

            CallbackQueryParameter = "callback";
        }

        public string CallbackQueryParameter { get; set; }

        public override bool CanWriteType(Type type) {
            if (type == typeof(JValue) || type == typeof(JObject) || type == typeof(JArray))
                return false;
            return base.CanWriteType(type);
        }

        public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, System.Net.Http.HttpRequestMessage request, MediaTypeHeaderValue mediaType) {
            var formatter = new JsonpMediaTypeFormatter()
            {
                CallbackQueryParameter = IsJsonpRequest(request)
            };

            // You have to reapply any JSON.NET default serializer Customizations here    
            //formatter.SerializerSettings.Converters.Add(new StringEnumConverter());
            //formatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

            return formatter;
        }

        public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext) {
            
            string callback = CallbackQueryParameter;

            if (string.IsNullOrEmpty(CallbackQueryParameter))
                return base.WriteToStreamAsync(type, value, stream, content, transportContext);
            

            StreamWriter writer = null;

            var ex = MethodHelper.Try(() => {
                writer = new StreamWriter(stream);
                writer.Write(callback + "(");
                writer.Flush();
            });

            if (ex != null) {
                MethodHelper.Try(() => {
                    if (writer != null) writer.Dispose();
                });
                var tcs = new TaskCompletionSource<object>();
                tcs.SetException(ex);
                return tcs.Task;
            }

            return base.WriteToStreamAsync(type, value, stream, content, transportContext)
                       .ContinueWith(innerTask =>
                       {
                           if (innerTask.Status == TaskStatus.RanToCompletion) {
                               writer.Write(")");
                               writer.Flush();
                           }

                       }, TaskContinuationOptions.ExecuteSynchronously)
                        .ContinueWith(innerTask =>
                        {
                            writer.Dispose();
                            return innerTask;

                        }, TaskContinuationOptions.ExecuteSynchronously)
                        .Unwrap();      
        }

        private string IsJsonpRequest(HttpRequestMessage request) {

            if (request.Method != HttpMethod.Get) return null;

            var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
            var callback = query[CallbackQueryParameter];

           
            return String.IsNullOrWhiteSpace(callback) ? null : callback;
        }
    }
}